/*
This file is part of MMM.

MMM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

MMM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with MMM.  If not, see <http://www.gnu.org/licenses/>.
*
* @package    MMM
* @author     Andre Meixner
* @copyright  2018 High Performance Humanoid Technologies (H2T), Karlsruhe, Germany
*
*/

#ifndef __MMM_PLUGINHANDLER_H_
#define __MMM_PLUGINHANDLER_H_

#include <MMM/MultipleFactoryPluginLoader.h>
#include <boost/signals2/signal.hpp>
#include <QStringList>
#include <QSettings>

class BasicPlugin
{
public:
    BasicPlugin(const std::string &name, const std::string &path, bool active = false) :
        name(name),
        path(path),
        active(active)
    {
    }

    std::string name;
    std::string path;
    bool active;
};

typedef boost::shared_ptr<BasicPlugin> BasicPluginPtr;

class IPluginHandler
{
public:
    virtual std::string getName() = 0;

    virtual void emitUpdate() = 0;

    virtual bool addPlugins(const std::vector<std::string> &paths) = 0;

    virtual bool addPlugins(const std::string &path) = 0;

    virtual std::map<std::string, std::vector<BasicPluginPtr> > getPlugins() = 0;

    virtual void setActivePlugin(const std::string &type, const std::string &path, bool active = true) = 0;
};

template <typename Factory>
class PluginHandler : public IPluginHandler
{

public:

    PluginHandler(const std::string &name, const std::string &defaultPath = std::string()) :
        name(name),
        defaultPath(defaultPath),
        factoryPluginLoader(new MMM::MultipleFactoryPluginLoader<Factory>()),
        qsettings_pluginpath(QString::fromStdString("pluginpaths/" + name))
    {
        if (settings.contains(qsettings_pluginpath)) {
            QStringList qpaths = settings.value(qsettings_pluginpath).toStringList();
            std::vector<std::string> paths;
            for (auto qpath : qpaths) paths.push_back(qpath.toStdString());
            if (loadPlugins(paths)) savePluginPaths();
        } else if (!defaultPath.empty()){
            loadPlugins(defaultPath);
            savePluginPaths();
        }

        initActivePluginsFromQSettings();
    }

    std::string getName() {
        return name;
    }

    boost::signals2::signal<void(std::map<std::string, boost::shared_ptr<Factory> >)> updateSignal;

    void emitUpdate() {
        updateSignal(factoryPluginLoader->getFactories());
    }

    bool addPlugins(const std::vector<std::string> &paths) {
        if (loadPlugins(paths)) {
            savePluginPaths();
            return true;
        }
        return false;
    }

    bool addPlugins(const std::string &path) {
        if (loadPlugins(path)) {
            savePluginPaths();
            return true;
        }
        return false;
    }

    std::map<std::string, std::vector<BasicPluginPtr> > getPlugins() {
        std::map<std::string, std::vector<BasicPluginPtr> > plugins;
        for (const auto &m : factoryPluginLoader->getAllFactories()) {
            auto factory = factoryPluginLoader->getFactories()[m.first];
            std::string activePath = factory ? factory->getPath() : std::string();
            for (const auto &f : m.second) {
                std::string path = f.second->getPath();
                plugins[m.first].push_back(BasicPluginPtr(new BasicPlugin(m.first, path, path == activePath)));
            }
        }
        return plugins;
    }

    void setActivePlugin(const std::string &type, const std::string &path, bool active = true) {
        if (factoryPluginLoader->setActivePlugin(type, path, active)) {
            qSettings_setActivePlugin(type, path, active);
            emitUpdate();
        }
    }

private:
    bool loadPlugins(const std::vector<std::string> &paths) {
        bool pluginsAdded = false;
        for (const auto &path : paths) pluginsAdded |= loadPlugins(path);
        return pluginsAdded;
    }

    bool loadPlugins(const std::string &path) {
        if (factoryPluginLoader->addPluginLibs(path)) {
            this->paths.push_back(path);
            return true;
        }
        else {
            MMM_INFO << "Ignoring path " << path << std::endl;
            return false;
        }
    }

    void savePluginPaths() {
        QStringList qpaths;
        for (auto path : paths) qpaths << QString::fromStdString(path);
        settings.setValue(qsettings_pluginpath, qpaths);
    }

    void initActivePluginsFromQSettings() {
        for (const auto &m : factoryPluginLoader->getAllFactories()) {
            auto currentActiveFactory = factoryPluginLoader->getFactories()[m.first];
            std::string currentActivePath = currentActiveFactory ? currentActiveFactory->getPath() : std::string();
            for (const auto &f : m.second) {
                std::string path = f.second->getPath();
                bool defaultActive = path == currentActivePath;
                bool active = defaultActive;
                if (settings.contains(qSettings_ActivePluginPath(m.first))) {
                    active = settings.value(qSettings_ActivePluginPath(m.first)).toString().toStdString() == path;
                    if (active != defaultActive) factoryPluginLoader->setActivePlugin(m.first, path, active);
                }
                else if (active) settings.setValue(qSettings_ActivePluginPath(m.first), QString::fromStdString(path));
            }
        }
    }

    void qSettings_setActivePlugin(const std::string &type, const std::string &path, bool active) {
        if (active) settings.setValue(qSettings_ActivePluginPath(type), QString::fromStdString(path));
        else if (settings.value(qSettings_ActivePluginPath(type), QString()).toString().toStdString() == path)
            settings.setValue(qSettings_ActivePluginPath(type), QString());
    }

    QString qSettings_ActivePluginPath(const std::string &type) {
        return QString::fromStdString("activeplugins/" + name + "/" + type);
    }

    std::string name;
    std::string defaultPath;
    boost::shared_ptr<MMM::MultipleFactoryPluginLoader<Factory> > factoryPluginLoader;
    std::vector<std::string> paths;
    QSettings settings;
    QString qsettings_pluginpath;
};

#endif // __MMM_PLUGINHANDLER_H_
